﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Windows.Controls;
using Microsoft.Windows.Data.DomainServices;
using MVVMtutorialAdvanced.Web;

namespace MVVMtutorialAdvanced.DomainServices
{
	public class DomainServiceBookLive : IDomainServiceBook
	{
		private readonly DomainServiceBook _service;

		public DomainServiceBookLive()
		{
			_service = new DomainServiceBook();
		}

		#region GetCountries

		private Action<IEnumerable<CountryDTO>, Exception> _getCountriesCallback;

		public void GetCountries(Action<IEnumerable<CountryDTO>, Exception> callback)
		{
			_getCountriesCallback = callback;

			_service.Authors.Clear();

			var query = _service.GetCountriesQuery();
			var loadOperation = _service.Load(query);
			loadOperation.Completed += getCitiesLoadOp_Completed;
		}

		void getCitiesLoadOp_Completed(object sender, EventArgs e)
			{
				if (_getCountriesCallback == null)
					return;

				var lo = sender as LoadOperation<CountryDTO>;

				if (lo == null)
					_getCountriesCallback(null, new NullReferenceException("LoadOperation is null"));
				else if (lo.HasError)
					_getCountriesCallback(null, lo.Error);
				else
					_getCountriesCallback(lo.Entities.AsEnumerable(), null);
			}

		#endregion GetCountries

		#region GetAuthorsQ

		private Action<IEnumerable<AuthorQDTO>, Exception> _getAuthorsQDTOCallback;

		public void GetAuthorsQ(Action<IEnumerable<AuthorQDTO>, Exception> callback)
		{
			_getAuthorsQDTOCallback = callback;

			//_service.AuthorQDTOs.Clear(); TODO

			var query = _service.GetAuthorsQQuery();
			var loadOperation = _service.Load(query);
			loadOperation.Completed += getAuthorsQLoadOp_Completed;
		}

		void getAuthorsQLoadOp_Completed(object sender, EventArgs e)
		{
			if (_getAuthorsQDTOCallback == null)
				return;

			var lo = sender as LoadOperation<AuthorQDTO>;

			if (lo == null)
				_getAuthorsQDTOCallback(null, new NullReferenceException("LoadOperation is null"));
			else if (lo.HasError)
				_getAuthorsQDTOCallback(null, lo.Error);
			else
				_getAuthorsQDTOCallback(lo.Entities.AsEnumerable(), null);
		}

		#endregion GetAuthorsQ

		private DomainDataSource _domainDataSourceAuthors;
		public void SetDomainDataSourceAuthors(DomainDataSource domainDataSource)
		{
			_domainDataSourceAuthors = domainDataSource;
			_domainDataSourceAuthors.DomainContext = _service;
			_domainDataSourceAuthors.QueryName = "GetAuthorsQuery";
			_domainDataSourceAuthors.LoadedData += LoadedDataAuthors;
			_domainDataSourceAuthors.AutoLoad = false;
		}

		#region GetAuthors

		private Action<Exception> _getAuthorsCallback;

		public void GetAuthors(Action<Exception> callback)
		{
			_getAuthorsCallback = callback;
			_domainDataSourceAuthors.Load();
		}

		void LoadedDataAuthors(object sender, LoadedDataEventArgs e)
		{
			if (_getAuthorsCallback == null)
				return;

			if (e.HasError)
			{
				_getAuthorsCallback(e.Error);
				e.MarkErrorAsHandled();
			}
			else
				_getAuthorsCallback(null);
		}

		#endregion GetAuthors


		#region InsertAuthor

		private Action<Exception> _insertAuthorCallback;

		public void InsertAuthor(Author author, Action<Exception> callback)
		{
			_insertAuthorCallback = callback;

			_service.Authors.Add(author);
			_service.SubmitChanges(OnInsertCompleted, null);
		}

		void OnInsertCompleted(SubmitOperation so)
		{
			if (_insertAuthorCallback == null)
				return;

			_insertAuthorCallback(so.Error);
		}

		#endregion InsertAuthor


		#region DeleteAuthor

		private Action<Exception> _deleteAuthorCallback;

		public void DeleteAuthor(Author author, Action<Exception> callback)
		{
			_deleteAuthorCallback = callback;

			_service.Authors.Remove(author);
			_service.SubmitChanges(OnDeleteCompleted, null);
		}

		void OnDeleteCompleted(SubmitOperation so)
		{
			if (_deleteAuthorCallback == null)
				return;

			_deleteAuthorCallback(so.Error);
		}

		#endregion DeleteAuthor


		#region UpdateAuthor

		private Action<Exception> _updateAuthorCallback;

		public void UpdateAuthor(Author author, Action<Exception> callback)
		{
			_updateAuthorCallback = callback;

			_service.SubmitChanges(OnSubmitCompleted, null);
		}

		void OnSubmitCompleted(SubmitOperation so)
		{
			if (_updateAuthorCallback == null)
				return;

			_updateAuthorCallback(so.Error);
		}

		#endregion UpdateAuthor


		#region GetBooks

		// http://blogs.msdn.com/b/kylemc/archive/2010/12/02/introducing-an-mvvm-friendly-domaindatasource-the-domaincollectionview.aspx?PageIndex=3#comments
		// http://blogs.msdn.com/b/kylemc/archive/2010/12/02/collection-binding-options-in-wcf-ria-services-sp1.aspx

		private Action<DomainCollectionView<BookDTO>, Exception> _getBooksCallback;
		private EntityList<BookDTO> _entityListBooks;
		private DomainCollectionViewLoader<BookDTO> _domainCollectionViewLoader;
		private DomainCollectionView<BookDTO> _domainCollectionViewBooks;

		public void GetBooks(Action<DomainCollectionView<BookDTO>, Exception> callback) //IList to make List editable
		{
			_getBooksCallback = callback;

			//Clear BookAuthors to be sure, changes on authors will be reflected !!!!!!!!!!!!!!!!!!!!!!!!!
			EntitySet s;
			if (_service.EntityContainer.TryGetEntitySet(typeof(BookAuthorDTO), out s))
				s.Clear();

			_entityListBooks = new EntityList<BookDTO>(_service.BookDTOs);

			_domainCollectionViewLoader = new DomainCollectionViewLoader<BookDTO>(LoadOperationGetBooks, OnGetBooksCompleted);
			_domainCollectionViewBooks = new DomainCollectionView<BookDTO>(_domainCollectionViewLoader, _entityListBooks);

			_domainCollectionViewBooks.SortDescriptions.Add(
					new SortDescription("Title", ListSortDirection.Ascending));

			// Go back to the first page when the sorting changes
			INotifyCollectionChanged notifyingSortDescriptions = _domainCollectionViewBooks.SortDescriptions;
			notifyingSortDescriptions.CollectionChanged += (sender, e) => _domainCollectionViewBooks.MoveToFirstPage();

			using (_domainCollectionViewBooks.DeferRefresh())
			{
				_domainCollectionViewBooks.PageSize = 5;
				_domainCollectionViewBooks.MoveToFirstPage();
			}
		}

		private LoadOperation<BookDTO> LoadOperationGetBooks()
		{
			var query = _service.GetBooksQuery().SortAndPageBy(_domainCollectionViewBooks); //SortPageAndCount replaced by SortAndPageBy 
			var loadOperation = _service.Load(query);
			
			return loadOperation;
		}

		private void OnGetBooksCompleted(LoadOperation<BookDTO> op)
		{
			if (op.HasError)
			{
				op.MarkErrorAsHandled();
				_getBooksCallback(null, op.Error);
			}
			else if (!op.IsCanceled)
			{
				_entityListBooks.Source = op.Entities;

				if (op.TotalEntityCount != -1)
					_domainCollectionViewBooks.SetTotalItemCount(op.TotalEntityCount);

				_getBooksCallback(_domainCollectionViewBooks, op.Error);
			}
		}

		#endregion GetBooks


		#region InsertBook

		private Action<Exception> _insertBookCallback;

		public void InsertBook(BookDTO book, Action<Exception> callback)
		{
			_insertBookCallback = callback;
			_service.SubmitChanges(OnInsertBookCompleted, null);
		}

		void OnInsertBookCompleted(SubmitOperation so)
		{
			if (_insertBookCallback == null)
				return;

			((IChangeTracking)_service.EntityContainer).AcceptChanges(); //Workaround

			_insertBookCallback(so.Error);
		}

		#endregion InsertBook


		#region DeleteBook

		private Action<Exception> _deleteBookCallback;

		public void DeleteBook(BookDTO book, Action<Exception> callback)
		{
			_deleteBookCallback = callback;

			_service.BookDTOs.Remove(book);
			_service.SubmitChanges(OnDeleteBookCompleted, null);
		}

		void OnDeleteBookCompleted(SubmitOperation so)
		{
			if (_deleteBookCallback == null)
				return;

			_deleteBookCallback(so.Error);
		}

		#endregion DeleteBook


		#region UpdateBook

		private Action<Exception> _updateBookCallback;

		public void UpdateBook(BookDTO book, Action<Exception> callback)
		{
			//var rem = _service.EntityContainer.GetChanges().RemovedEntities;
			//var mod = _service.EntityContainer.GetChanges().ModifiedEntities;
			//var add = _service.EntityContainer.GetChanges().AddedEntities;
			//EntitySet s;
			//_service.EntityContainer.TryGetEntitySet(typeof (BookAuthorDTO), out s);

			_updateBookCallback = callback;
			_service.SubmitChanges(OnSubmitBookCompleted, null);	
		}

		void OnSubmitBookCompleted(SubmitOperation so)
		{
			if (_updateBookCallback == null)
				return;

			//var rem = _service.EntityContainer.GetChanges().RemovedEntities;
			//var mod = _service.EntityContainer.GetChanges().ModifiedEntities;
			//var add = _service.EntityContainer.GetChanges().AddedEntities;
			//EntitySet s;
			//_service.EntityContainer.TryGetEntitySet(typeof(BookAuthorDTO), out s);

			// see http://stackoverflow.com/questions/2899696/domaincontext-sometimes-still-haschanges-after-submitchanges-completes
			// and http://forums.silverlight.net/forums/p/183798/418701.aspx#418701
			// Bug: Yes, I've confirmed this is a bug. Thanks for reporting it and providing the repro. As you discovered, the bug will only repro in composition scenarios where the parent has been modified in addition to one or more children. The workaround is to do an explicit AcceptChanges if the submit was successful.
			((IChangeTracking)_service.EntityContainer).AcceptChanges();

			_updateBookCallback(so.Error);
		}

		#endregion UpdateBook

		public void RejectChanges()
		{
			if (_service.DomainClient.SupportsCancellation)
				_service.RejectChanges();
		}

		public void AcceptChanges()
		{
			((IChangeTracking)_service.EntityContainer).AcceptChanges();
		}
	}
}
